package vn.co.thn.android.serviceconnect.request;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import vn.co.thn.android.serviceconnect.AppBase;
import vn.co.thn.android.serviceconnect.callback.NetWorkCallBack;
import vn.co.thn.android.serviceconnect.callback.RequestCallBack;
import vn.co.thn.android.serviceconnect.callback.RequestError;
import vn.co.thn.android.serviceconnect.callback.RequestProcessCallBack;
import vn.co.thn.android.serviceconnect.parse.BaseParse;
import vn.co.thn.android.serviceconnect.request.ServiceTask.TaskRequest;

import com.android.volley.AuthFailureError;
import com.android.volley.Cache;
import com.android.volley.Cache.Entry;
import com.android.volley.NetworkResponse;
import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.HttpHeaderParser;
import com.android.volley.toolbox.StringRequest;

public class RequestService {
	private String mUrl = null;
	private ArrayList<Param> mParams = null;
	private String mKey = null;
	private Object mDataCache = null;
	@SuppressWarnings("rawtypes")
	private RequestCallBack mRequestCallBack = null;
	private RequestError mRequestError = null;
	private RequestProcessCallBack mRequestProcessCallBack = null;
	private NetWorkCallBack mNetWorkCallBack = null;
	private Class<?> mJsonResult;
	private AppBase mAppBase;
	private TaskRequest mTaskRequest;

	public <T> RequestService(AppBase appBase) {
		mAppBase = appBase;
	};

	public <T> RequestService(String url, Class<T> clazz, RequestCallBack<T> requestCallBack, AppBase Application) {
		this.mAppBase = Application;
		mRequestCallBack = requestCallBack;
		mUrl = url;
		mJsonResult = clazz;

	}

	@SuppressWarnings("unchecked")
	public <T> void setRequestCallBack(RequestCallBack<T> callBack) {
		this.mJsonResult = (Class<T>) ((ParameterizedType) callBack.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];

		mRequestCallBack = callBack;
	}

	public void setTaskRequest(TaskRequest taskRequest) {
		mTaskRequest = taskRequest;
	}

	public void setRequestError(RequestError requestError) {
		mRequestError = requestError;
	}

	public void setUrl(String url) {
		this.mUrl = url;
	}

	public void setParams(ArrayList<Param> params) {
		this.mParams = params;
	}

	private <T> void Request(int method) {
		CreateKey();
		if (mRequestProcessCallBack != null) {
			mRequestProcessCallBack.ShowProcess();
		}
		StringRequest stringRequest = new RequestJson(method, mUrl, new Listener<String>() {

			@Override
			public void onResponse(String response) {
				// TODO Auto-generated method stub
				if (mTaskRequest != null) {
					mTaskRequest.TaskComplete();
				}
				if (mRequestCallBack == null) {
					return;
				}
				SetResult(response);
			}

		}, new ErrorListener() {

			@Override
			public void onErrorResponse(VolleyError error) {
				// TODO Auto-generated method stub
				if (mTaskRequest != null) {
					mTaskRequest.TaskComplete();
				}
				if (mRequestError != null) {
					mRequestError.onError(error);
				}
				if (mRequestProcessCallBack != null) {
					mRequestProcessCallBack.HideProcess();
				}
				if (error.networkResponse == null) {
					if (mDataCache != null) {
						if (mNetWorkCallBack != null)
							mNetWorkCallBack.NetWorkError(false);
					} else {
						if (mNetWorkCallBack != null)
							mNetWorkCallBack.NetWorkError(true);
					}
				}

			}
		});
		mAppBase.addToRequestQueue(stringRequest, mKey);
	}

	@SuppressWarnings("unchecked")
	private <T> void RequestAndCache(int method) {
		CreateKey();
		if (mRequestProcessCallBack != null) {
			mRequestProcessCallBack.ShowProcess();
		}
		if (mDataCache != null) {
			try {
				if (mDataCache instanceof JSONObject) {
					mRequestCallBack.onResult(BaseParse.ParserJsonToObject(new JSONObject(mDataCache.toString()), mJsonResult), true);
				} else {
					JSONArray jsonArray = new JSONArray(mDataCache.toString());
					ArrayList<T> lst = new ArrayList<T>();
					for (int i = 0; i < jsonArray.length(); i++) {
						lst.add((T) BaseParse.ParserJsonToObject(jsonArray.getJSONObject(i), mJsonResult));
					}
					mRequestCallBack.onResult(lst, true);
				}
			} catch (Exception e) {
				mRequestError.onException(e);
				e.printStackTrace();
			}
		}
		StringRequest stringRequest = new RequestJson(method, mUrl, new Listener<String>() {

			@Override
			public void onResponse(String response) {
				// TODO Auto-generated method stub
				if (mTaskRequest != null) {
					mTaskRequest.TaskComplete();
				}
				if (mRequestCallBack == null) {
					return;
				}
				if (mDataCache != null) {
					if (!response.equalsIgnoreCase(mDataCache.toString())) {
						SetResult(response);
					}
				} else {
					SetResult(response);
				}
				mAppBase.getRequestQueue().getCache().invalidate(mKey, true);
			}

		}, new ErrorListener() {

			@Override
			public void onErrorResponse(VolleyError error) {
				// TODO Auto-generated method stub
				if (mTaskRequest != null) {
					mTaskRequest.TaskComplete();
				}
				if (mRequestError != null) {
					mRequestError.onError(error);
				}
				if (mRequestProcessCallBack != null) {
					mRequestProcessCallBack.HideProcess();
				}
				if (error.networkResponse == null) {
					if (mDataCache != null) {
						if (mNetWorkCallBack != null)
							mNetWorkCallBack.NetWorkError(false);
					} else {
						if (mNetWorkCallBack != null)
							mNetWorkCallBack.NetWorkError(true);
					}
				}

			}
		});
		mAppBase.addToRequestQueue(stringRequest, mKey);
	}

	public void PostAndCache() {
		RequestAndCache(Method.POST);
	}

	public void GetAndCache() {
		RequestAndCache(Method.GET);
	}

	public void Post() {
		Request(Method.POST);
	}

	public void Get() {
		Request(Method.GET);
	}

	@SuppressWarnings("unchecked")
	private <T> void SetResult(String response) {
		if (mTaskRequest != null) {
			mTaskRequest.TaskComplete();
		}
		try {
			if (ConvetString(response) instanceof JSONObject) {
				mRequestCallBack.onResult(BaseParse.ParserJsonToObject(response, mJsonResult), true);
				mAppBase.getRequestQueue().getCache().invalidate(mKey, true);
			} else {
				JSONArray jsonArray = new JSONArray(response);
				ArrayList<T> lst = new ArrayList<T>();
				for (int i = 0; i < jsonArray.length(); i++) {
					lst.add((T) BaseParse.ParserJsonToObject(jsonArray.getJSONObject(i), mJsonResult));
				}
				mRequestCallBack.onResult(lst, true);
			}
		} catch (Exception e) {
			mRequestError.onException(e);
			e.printStackTrace();

		}
		if (mRequestProcessCallBack != null) {
			mRequestProcessCallBack.HideProcess();
		}
		if (mNetWorkCallBack != null) {
			mNetWorkCallBack.NetWorkSuccess();
		}
	}

	//

	public void setRequestProcessCallBack(RequestProcessCallBack requestProcessCallBack) {
		this.mRequestProcessCallBack = requestProcessCallBack;
	}

	public void setNetWorkCallBack(NetWorkCallBack netWorkCallBack) {
		this.mNetWorkCallBack = netWorkCallBack;
	}

	private Object ConvetString(String str) {
		try {
			return new JSONObject(str);
		} catch (JSONException ex) {
			try {
				return new JSONArray(str);
			} catch (JSONException e) {
				return null;
			}
		}
	}

	private void CreateKey() {
		JSONObject obj = new JSONObject();
		mKey = "";
		if (mParams != null && mParams.size() > 0) {
			// mKey = mParams.toString();
			for (Param param : mParams) {
				try {
					obj.put(param.getParamName(), param.getParamValue().toString());
				} catch (JSONException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					mKey = "";
				}
			}
			mKey = obj.toString();

		} else {
			mKey = "";
		}
		mKey = mUrl + mKey;
		mDataCache = getCache();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public <T> void LoadCache(RequestCallBack requestCallBack) {
		CreateKey();
		if (mDataCache != null) {
			try {
				if (mDataCache instanceof JSONObject) {
					requestCallBack.onResult(BaseParse.ParserJsonToObject(new JSONObject(mDataCache.toString()), mJsonResult), true);
				} else {
					JSONArray jsonArray = new JSONArray(mDataCache.toString());
					ArrayList<T> lst = new ArrayList<T>();
					for (int i = 0; i < jsonArray.length(); i++) {
						lst.add((T) BaseParse.ParserJsonToObject(jsonArray.getJSONObject(i), mJsonResult));
					}
					requestCallBack.onResult(lst, true);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public Object getCache() {
		try {
			Cache cache = mAppBase.getRequestQueue().getCache();
			Entry entry = cache.get(this.mKey);
			if (entry != null) {
				try {
					String data = new String(entry.data, "UTF-8");
					return ConvetString(data);
				} catch (UnsupportedEncodingException e) {
					return null;
				}
			} else {
				return null;
			}
		} catch (Exception e) {
			return null;
		}
	}

	// request stringjson
	class RequestJson extends StringRequest {
		private boolean isMultipart = false;
		private HttpEntity mHttpEntity;
		private String path_file;
		private File file;
		private String mFile_Key;

		public RequestJson(int method, String url, Listener<String> listener, ErrorListener errorListener) {
			super(method, url, listener, errorListener);
			// TODO Auto-generated constructor stub
		}

		public RequestJson(int method, String url, String Path_file, String File_Key, Listener<String> listener, ErrorListener errorListener) {
			super(method, url, listener, errorListener);
			setPathFile(Path_file);
			isMultipart = true;
			mFile_Key = File_Key;
			mHttpEntity = buildMultipartEntity();
		}

		public RequestJson(int method, String url, File file, String File_Key, Listener<String> listener, ErrorListener errorListener) {
			super(method, url, listener, errorListener);
			setFile(file);
			isMultipart = true;
			mFile_Key = File_Key;
			mHttpEntity = buildMultipartEntity();
		}

		@Override
		public String getCacheKey() {
			// TODO Auto-generated method stub
			return mKey;
		}

		@Override
		protected Map<String, String> getParams() throws AuthFailureError {
			Map<String, String> Params = new HashMap<String, String>();
			if (mParams != null) {
				for (Param param : mParams) {
					Params.put(param.getParamName().toString(), param.getParamValue().toString());
				}
			}
			return Params;
		}

		@Override
		public String getBodyContentType() {
			if (this.isMultipart) {
				return mHttpEntity.getContentType().getValue();
			}
			return super.getBodyContentType();
		}

		@Override
		public byte[] getBody() throws AuthFailureError {
			if (isMultipart) {
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				try {
					mHttpEntity.writeTo(bos);
				} catch (IOException e) {
					VolleyLog.e("IOException writing to ByteArrayOutputStream");
				}
				return bos.toByteArray();
			}
			return super.getBody();
		}

		@Override
		protected Response<String> parseNetworkResponse(NetworkResponse response) {
			String parsed;
			try {
				parsed = new String(response.data, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				parsed = new String(response.data);
			}
			return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
		}

		private HttpEntity buildMultipartEntity() {
			MultipartEntityBuilder builder = MultipartEntityBuilder.create();
			if (this.file != null) {
				FileBody fileBody = new FileBody(file);
				builder.addPart(mFile_Key, fileBody);
			}
			for (Param param : mParams) {
				builder.addTextBody(param.getParamName(), param.getParamValue().toString());
			}
			return builder.build();
		}

		public void setMultipart(boolean Multipart) {
			this.isMultipart = Multipart;
		}

		private void setPathFile(String path) {
			this.path_file = path;
			file = new File(path_file);
		}

		private void setFile(File f) {
			this.file = f;
		}
	}
}
